import type { Request, Response } from 'express';

import { GitLog } from '@/types';
import { AppModel, IAppDetails } from '@models/app';
import { IRepo, IRepoDetail } from '@models/repo';
import { spawn } from 'child_process';
import fs from 'fs';
import { join } from 'path';
import { z } from 'zod';

import config from '../../../config';
import Resp from '../../../utils/response';

// 参数校验规则
export const validator = z.object({
  ID: z
    .string({
      invalid_type_error: '应用ID必须是字符串',
      required_error: '应用ID不能为空',
    })
    .min(1, '仓库ID不能为空'),
});

/**
 * 获取应用详情API
 * 包含仓库信息和更新状态
 */
export const api = async (req: Request, res: Response): Promise<Response> => {
  try {
    const { ID } = req.params as z.infer<typeof validator>;

    // 查询应用信息，并关联仓库数据
    const result = await AppModel.findById(ID).populate({
      path: 'repository',
      select: 'name type git branch',
    });
    if (!result) {
      return Resp.error(res, 404, '应用不存在');
    }

    const app: IAppDetails = result.toObject();

    // 应用数据存储路径
    const appDirectory = join(config.dataPath, ID);

    // 并行处理所有仓库，获取本地日志和更新状态
    const repositoryWithStatus = await Promise.all(
      app.repository.map(async (repo: IRepo) => {
        try {
          // 获取本地仓库路径
          const localRepoPath = join(appDirectory, repo.name);
          // 获取本地最新提交日志
          const localLastLog = await getLocalRepoNewLog(localRepoPath);

          // 检查是否需要更新
          let needUpdate = false;
          if (localLastLog) {
            try {
              needUpdate = await checkRepoNeedUpdate(localRepoPath);
            } catch (error) {
              console.warn(`检查仓库更新状态失败 [${repo.name}]:`, error);
            }
          }

          // 返回带有状态信息的仓库对象
          return {
            ...repo,
            localLastLog,
            needUpdate,
          } as IRepoDetail;
        } catch (error) {
          console.warn(`获取仓库状态失败 [${repo.name}]:`, error);
          return {
            ...repo,
            localLastLog: null,
            needUpdate: false,
          } as IRepoDetail;
        }
      }),
    );

    // 更新应用的仓库信息
    app.repository = repositoryWithStatus as unknown as IRepoDetail[];

    // 返回成功响应
    return Resp.success(res, app, '获取应用详情成功');
  } catch (error) {
    console.error(error);
    return Resp.catch(res, error);
  }
};

/**
 * 获取本地仓库的最新提交日志
 */
async function getLocalRepoNewLog(repoPath: string): Promise<GitLog | null> {
  try {
    await fs.promises.access(repoPath, fs.constants.F_OK);
  } catch (e) {
    return null;
  }
  return new Promise<GitLog>((resolve, reject) => {
    const git = spawn('git', ['log', '-1', '--pretty="format:%H|%an|%ad|%s"', '--date=iso'], {
      cwd: repoPath,
      shell: true,
    });

    let output = '';
    git.stdout.on('data', data => {
      output += data.toString();
    });

    git.stderr.on('data', () => {});

    git.on('close', code => {
      if (code === 0 && output) {
        const [hash, author, date, ...messageArr] = output.split('|');
        resolve({
          author,
          date,
          hash,
          message: messageArr.join('|'),
        });
      } else {
        reject(new Error('未获取到提交日志'));
      }
    });

    git.on('error', err => {
      reject(err);
    });
  });
}

/**
 * 检查本地仓库是否需要更新
 * @param repoPath 本地仓库路径
 * @returns 是否需要更新
 */
async function checkRepoNeedUpdate(repoPath: string): Promise<boolean> {
  return new Promise<boolean>((resolve, reject) => {
    // 先执行 git remote update 获取最新的远程信息
    const gitRemoteUpdate = spawn('git', ['remote', 'update'], {
      cwd: repoPath,
      shell: true,
    });

    gitRemoteUpdate.on('close', code => {
      if (code !== 0) {
        console.warn(`git remote update 失败，代码: ${code}`);
      }

      // 比较本地和远程的提交差异
      const gitStatus = spawn('git', ['status', '-uno'], {
        cwd: repoPath,
        shell: true,
      });

      let output = '';
      gitStatus.stdout.on('data', data => {
        output += data.toString();
      });

      gitStatus.stderr.on('data', () => {});

      gitStatus.on('close', statusCode => {
        if (statusCode === 0) {
          // 检查输出中是否包含 "behind" 字样，表示本地落后于远程
          const needUpdate = output.includes('behind') || output.includes('落后');
          resolve(needUpdate);
        } else {
          reject(new Error(`git status 命令执行失败，代码: ${statusCode}`));
        }
      });

      gitStatus.on('error', err => {
        reject(err);
      });
    });

    gitRemoteUpdate.on('error', err => {
      reject(err);
    });
  });
}
